﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using TransportAbstractionLibrary.Common.BufferPool;
using TransportAbstractionLibrary.Exceptions;
using TransportAbstractionLibrary.Common;

namespace TransportAbstractionLibrary.TCP
{
    public sealed partial class TCPProtocolBase : IProtocolLowerLayer, IDisposable, IAsyncHost
    {
        Guid id = new Guid("BAFBD487-B924-4635-B26F-98306E26BCC1");
        Socket socket = null;
        BufferManager bufferManager;
        IProtocolUpperLayer upperLayer;
        object locker = new object();
        bool suppressInitialization;

        public TCPProtocolBase(Socket socket, BufferManager bufferManager)
        {
            if (!socket.Connected)
            {
                throw new TALException(this, "Socket provided must be connected!");
            }

            this.socket = socket;
            this.bufferManager = bufferManager;

            InitializeSend();
            InitializeRead();
        }

        #region IProtocolBase Members

        public Guid Id
        {
            get
            {
                return id;
            }
        }

        public LayerType LayerType
        {
            get { return LayerType.Transport | LayerType.Order | LayerType.Integrity; }
        }

        public void Initialize()
        {
            StartReciving();
            upperLayer.Initialize();
        }

        public bool SuppressInitialization
        {
            get { return suppressInitialization; }
            set { suppressInitialization = value; }
        }

        #endregion

        #region IProtocolLowerLayer Members


        public void Bind(IProtocolUpperLayer upperLayer)
        {
            if (this.upperLayer == null)
            {
                this.upperLayer = upperLayer;
                upperLayer.Bind(this);
            }
            else
            {
                throw new ArgumentException("Can only be run once!");
            }
        }

        public IProtocolUpperLayer UpperLayer
        {
            get { return this.upperLayer; }
        }

        #endregion

        #region IDisposable Members

        private bool disposed = false;

        public void Dispose()
        {
            if (disposed)
            {
                return;
            }
            lock (sendingLocker)
            {
                lock (receivingLock)
                {
                    disposed = true;
                    GC.SuppressFinalize(this);
                    upperLayer.ConnectionClosed();
                    DisposeData();
                }
            }
        }


        /// Disconnect from the host.
        void Disconnect()
        {
            //TODO: unhoock all events to eventargs
            try
            {
                socket.Shutdown(SocketShutdown.Both);
                socket.Disconnect(false);
                socket.Close();
            }
            catch (SocketException ex)
            {
                HandleException(ex);
            }
        }

        void DisposeData()
        {
            Disconnect();
            DisposeRead();
            DisposeSend();
            if (upperLayer != null)
            {
                upperLayer = null;
            }
        }

        ~TCPProtocolBase()
        {
            DisposeData();
        }
        #endregion


        #region IProtocolLowerLayer Members

        #endregion

        private void HandleException(Exception ex)
        {
            System.Threading.Thread.MemoryBarrier();
            if (ex.GetType() == typeof(SocketException))
            {
                SocketException socketEx = (SocketException)ex;
                upperLayer.OnException(new TALException(this, ex));
                return;
            }
            else if (ex.GetType() == typeof(TALSocketException))
            {
                TALSocketException talSocketEx = (TALSocketException)ex;
                if (talSocketEx.SocketError == SocketError.ConnectionAborted)
                {
                    Dispose();
                    return;
                }
                else if (talSocketEx.SocketError == SocketError.ConnectionReset)
                {
                    Dispose();
                    return;
                }
            }
            if (!socket.Connected)
            {
                Dispose();
                return;
            }
            upperLayer.OnException(new TALException(this, ex.Message,ex));
        }


        #region IAsyncHost Members


        IProtocolBase IAsyncHost.GetProtocolBase(SocketEventArgs args)
        {
            return this;
        }

        #endregion
    }
}
